/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ai;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.LinkedList;
/**
 *
 * @author root
 */
public class LexiconParser {
    
    public LexiconParser(){
        
    }
    
    public KnowledgeBase load(String pFileName) throws FileNotFoundException, IOException, ParseException{
        String lFileContents = loadFileContents(pFileName);
        lFileContents = lFileContents.trim();
        String lStringArray[] = lFileContents.split(";");
        
        mMapExpList = new LinkedList<MapExp>();
        mGramExpList = new LinkedList<ImplyExp>();
        for (int i = 0; i < lStringArray.length; i++){
            lStringArray[i] = lStringArray[i].trim();
            if(lStringArray[i].startsWith("#")){
                continue;
            }
            mCurrentExpNr = i + 1;
            mCurrentExp = lStringArray[i];
            // Assume we have an imply expression...
            String lSplitStr[] = lStringArray[i].split("<-");
            
            if (lSplitStr.length == 1) {
                // We don't have an imply expression, try a map expression.
                // Remove white spaces.
		lStringArray[i] = lStringArray[i].replaceAll("\\s", "");
                lSplitStr = lStringArray[i].split(":");
                mMapExpList.add(parseMapExp(lSplitStr));
            }
            else if (lSplitStr.length == 2) {
                // We have an imply expression!
                    mGramExpList.add(parseImplyExp(lSplitStr[0], lSplitStr[1]));
            }
            else {
                throw new ParseException("Unknown expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
            }
        }
        return new KnowledgeBase(mMapExpList, mGramExpList);
    }
    /*public LinkedList<Expression> parseString(String pStr){
        String lSplit[] = pStr.split("\s"); // split on white space
        
        
    }*/
    
    private String loadFileContents(String pFileName) throws FileNotFoundException, IOException{
        FileInputStream lFIS = new FileInputStream(new File(pFileName));
        InputStreamReader lISR = new InputStreamReader(lFIS);
        char lCharArray[] = new char[lFIS.available()];
        lISR.read(lCharArray, 0, lCharArray.length);
        lISR.close();
        lFIS.close();
        return new String(lCharArray);
    }
    
    private MapExp parseMapExp(String pList[]) throws ParseException{
        LabelExp [] lLabels = new LabelExp[pList.length];
        for(int i=0; i < pList.length ; i++){
            if(pList[i].matches("[A-Z0-9]+")){
                lLabels[i] = parseLabelExp(pList[i]);
            }
            else if(i == (pList.length - 1) && pList[i].matches("[A-Z0-9]+\\[.+")){
                int lIndex = pList[i].indexOf('[');
                String lLeft = pList[i].substring(0, lIndex);
                String lRight = pList[i].substring(lIndex, pList[i].length());
                
                lLabels[i] = parseLabelExp(lLeft);
                lRight = checkString(lRight,"[","]");
                
                String lMappings[] = null;
                if (i > 0){
                    lRight = checkString(lRight,"(",")");
                    lMappings = lRight.split("\\),\\(");
                }
                else {
                    lMappings = lRight.split(",");                    
                }

                MapExp lMapExp = new MapExp(lLabels);
                for(int j=0 ; j < lMappings.length ; j++){
                    parseMapping(lMapExp, lMappings[j]);
                }
                return lMapExp;
            }
            else{
                throw new ParseException("Invalid map expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
            }
        }
        return null;
    }
    
    private void parseMapping(MapExp pMapExp, String pMapping) throws ParseException{
        String lList[] = pMapping.split(",");
        if (lList.length < 1){
            throw new ParseException("parseMapping: Invalid map expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
        }
        if(lList[0].matches("[A-Z0-9]+")){
            MapExp lMExp = findMapExp(lList[0]);
            if(lMExp != null){
                HashSet<String> lSet = lMExp.getSet( lMExp.findLabel( lList[0] ) );
                for(String s : lSet){
                    String lMapping[] = new String[lList.length];
                    lMapping[0] = s;
                    int i;
                    for (i = 1; i < lList.length; i++){
                        LexExp lExp = parseMapSubExp(lList[i]);
                        if( !(lExp instanceof BoolExp) ){
                            LexExp e = null;
                            try{
                                e = lExp.substitute(lList[0], s);
                            }
                            catch(Exception someE){
                                throw new ParseException("Exception while substituting " + lList[0] + " for " + s + "\n" + someE.getMessage());
                            }
                            if(e instanceof StringExp){
                                lMapping[i] = ((StringExp)e).getString();
                            }
                            else if(e instanceof CondExp){
                                LexExp lReplacementExp = ((CondExp)e).evalCond();
                                if (lReplacementExp != null){
                                    // If lReplacementExp is not a StringExp, let the JVM kill this program. KILL KILL KILL!!! >:)
                                    lMapping[i] = ((StringExp)lReplacementExp).getString();
                                }
                                else {
                                    // Break the loop! This mapping should NOT be added!
                                    break;
                                }
                            }
                            else{
                                throw new ParseException("parseMapping: Expression "+ e.toString() + " doesn't evaluate to a StringExp or CondExp at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
                            }
                        }
                        else{
                            throw new ParseException("parseMapping: String "+ lList[1] + " evaluates to a BoolExp! at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
                        }
                    }
                    
                    // Only add the mapping if the for-loop wasn't interrupted.
                    if (i == lList.length){
                        pMapExp.addMapping(lMapping);
                    }
                }
                return;
            }
            else{
                throw new ParseException("parseMapping: Invalid label " + lList[0] + " at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
            }
        }
        for(int i=0; i < lList.length ; i++){
            lList[i] = checkString(lList[i],"\"","\"");
        }
        pMapExp.addMapping(lList);
    }
    private MapExp findMapExp(String pLabel){
        for( LexExp e : mMapExpList){
            if(e instanceof MapExp){
                MapExp lMExp = (MapExp)e;
                if (lMExp.findLabel(pLabel) != -1 ){
                    return lMExp;
                }
            }
        }
        return null;
    }
    
    private LexExp parseMapSubExp(String pRight) throws ParseException{
        if(pRight.matches(".+[?].+")){
            String lSplit[] = pRight.split("[?]");
            if(lSplit.length == 2){
                CondExp lExp = new CondExp();
                lExp.setLeft(parseMapSubExp(lSplit[0]));
                lExp.setRight(parseMapSubExp(lSplit[1]));
                
                return lExp;
            }
            else{
                throw new ParseException("parseSubExp: Invalid expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp); 
            }
        }
        else if(pRight.matches(".+[+].+")){
            String lSplit[] = pRight.split("[\\+]");
            if(lSplit.length > 1){
                ConcatExp lExp = new ConcatExp();
                for(int i=0; i < lSplit.length ; i++){
                    lExp.addExp(parseMapSubExp(lSplit[i]));
                }
                return lExp;
            }
            else{
                throw new ParseException("parseSubExp: Invalid expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
            }
        }
        else if(pRight.matches(".+[|].+")){
            String lSplit[] = pRight.split("[\\|]");
            if(lSplit.length > 1){
                OrExp lExp = new OrExp();
                for(int i=0; i < lSplit.length ; i++){
                    lExp.addExp(parseMapSubExp(lSplit[i]));
                }
                return lExp;
            }
            else{
                throw new ParseException("parseSubExp: Invalid expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp);             
            }
        }
        else if(pRight.matches(".+[&].+")){
            String lSplit[] = pRight.split("\\&");
            if(lSplit.length > 1){
                AndExp lExp = new AndExp();
                for(int i=0; i < lSplit.length ; i++){
                    lExp.addExp(parseMapSubExp(lSplit[i]));
                }
                return lExp;
            }
            else{
                throw new ParseException("parseSubExp: Invalid expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp); 
            }
        }
        else if(pRight.matches(".+(!=).+")){
            String lSplit[] = pRight.split("!=");
            //System.out.println("length " + lSplit.length + " pRight " + pRight);
            if(lSplit.length == 2){
                NotEqExp lExp = new NotEqExp();
                lExp.setLeft(parseMapSubExp(lSplit[0]));
                lExp.setRight(parseMapSubExp(lSplit[1]));
                
                return lExp;
            }
            else{
                throw new ParseException("parseSubExp: Invalid expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp); 
            }
        }
        else if(pRight.matches(".+(==).+")){
            String lSplit[] = pRight.split("==");
            if(lSplit.length == 2){
                EqExp lExp = new EqExp();
                lExp.setLeft(parseMapSubExp(lSplit[0]));
                lExp.setRight(parseMapSubExp(lSplit[1]));
                
                return lExp;
            }
            else{
                throw new ParseException("parseSubExp: Invalid expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp); 
            }
        }
        else if(pRight.matches("[A-Z0-9]+\\[-?[0-9]*(\\.\\.)?-?[0-9]*\\]")){
            int lIndex = pRight.indexOf("[");
            //System.out.println(lIndex);
            String lLeft = pRight.substring(0, lIndex);
            String lRight = pRight.substring(lIndex+1,pRight.length()-1);
            IndexExp lExp = new IndexExp();
            lExp.setLabel(parseLabelExp(lLeft));
            lExp.setRange(lRight);
            
            return lExp;
        }
        else if(pRight.matches("[A-Z0-9]+")){
            return new LabelExp(pRight);
        }
        else if(pRight.matches("[a-z]+")){
            //System.out.println("pRight " + pRight);
            return new StringExp(pRight);
        }
        else
        {
            throw new ParseException("parseSubExp: Parser gets confused by the string \"" + pRight + "\" at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
        }
    }
    
    private ImplyExp parseImplyExp(String pLeft, String pRight) throws ParseException{
        return new ImplyExp(parseLabelExp(pLeft), parseImplySubExp(pRight.trim()));
    }
    private GramExp parseImplySubExp(String pStr) throws ParseException{
        if(pStr.matches("[\\S\\s]+[\\|][\\S\\s]+")){
            String lSplit[] = pStr.split("[\\|]");
            if(lSplit.length > 1){
                ThatOtherOrExp lExp = new ThatOtherOrExp();
                for(int i=0; i < lSplit.length ; i++){
                    lExp.addExp((SerialExp)parseImplySubExp(lSplit[i].trim()));
                }
                return lExp;
            }
            else{
                throw new ParseException("parseImplySubExp: Invalid expression at rule " + mCurrentExpNr + ":\n" + mCurrentExp);             
            }
        }
        else if(pStr.matches("([A-Z0-9]+\\s*)+")){
            pStr = pStr.trim();
            String lSplit[] = pStr.split("\\s");
            SerialExp lExp = new SerialExp();
            for(int i=0; i < lSplit.length ; i++){
                lExp.addExp( parseLabelExp( lSplit[i] ) );
            }
            return lExp;
        }
        else if(pStr.matches("([A-Z0-9]+\\s*)+<.+>")){
            //System.out.println("Matches semantic exp thingy " + pStr);
            String lSplit[] = pStr.split("<");
            SerialExp lSE = (SerialExp)parseImplySubExp(lSplit[0].trim());
            lSplit[1] = lSplit[1].substring(0, lSplit[1].length()-1).trim();
            if(lSplit[1].startsWith("\\")){
                int lIndex = lSplit[1].indexOf(" ");
                String lVariables = lSplit[1].substring(1, lIndex).trim();
                String lOperation = lSplit[1].substring(lIndex+1, lSplit[1].length() );
                
                String lVarSplit[] = lVariables.split("\\.");
                
                boolean lGroup[] = new boolean[lVarSplit.length];
                for(int i = 0 ; i < lVarSplit.length ; i++){
                    lGroup[i] = lVarSplit[i].endsWith("+");
                    if (lGroup[i]){
                        lVarSplit[i] = lVarSplit[i].substring(0, lVarSplit[i].length()-1);
                    }
                }

                SemanticExp lOpExp = new SemanticExp("semexp", SemanticExp.SEMEXP);
                for(int i = 0 ; i < lVarSplit.length ;  i++){
                    lOpExp.addInput(lVarSplit[i], lGroup[i]);
                }
                //System.out.println("parseOperation " + pStr + ", " + lVarSplit);
                parseOperation(lOperation, 0, lOpExp, lVarSplit, lGroup);
                lSE.mSemExp = lOpExp;
            }
            else{
                SemanticExp lOpExp = new SemanticExp("semexp", SemanticExp.SEMEXP);
                parseOperation(lSplit[1], 0, lOpExp, null, null);
                lSE.mSemExp = lOpExp;
            }
            return lSE;
        }
        throw new ParseException("parseImplySubExp: Invalid expression " + pStr + " at rule " + mCurrentExpNr + ":\n" + mCurrentExp);      
    }
    private int parseOperation(String pStr, int pStartIndex, SemanticExp pOpExp, String pVars[], boolean pGroup[]) throws ParseException{
        //System.out.println("parsing semexp: " + pStr + " start index " + pStartIndex);
        int lIndex = pStartIndex;
        while(lIndex < pStr.length()){
            int lPrevIndex = lIndex;
            lIndex = findFirst(pStr, lIndex, "(),");
            String lOpName = pStr.substring(lPrevIndex, lIndex).trim();

            // If this is the first iteration, we found a ')' but no lOpName, break.
            if(lPrevIndex == pStartIndex && lIndex < pStr.length() && pStr.charAt(lIndex) == ')' && lOpName.isEmpty()){
                break;
            }
            
            SemanticExp lOpExp = null;
            if (lIndex == pStr.length() || pStr.charAt(lIndex) == ',' || pStr.charAt(lIndex) == ')'){
                if(lOpName.matches("[0-9]+")){
                    lOpExp = new SemanticExp(lOpName,SemanticExp.CONST);
                }
                else if(lOpName.matches("[A-Z0-9]+")){
                    lOpExp = new SemanticExp(lOpName,SemanticExp.LABEL);
                }
                else if(lOpName.matches("[A-Z0-9]+:[A-Z0-9]+")){
                    String lLabels[] = lOpName.split(":");
                    lOpExp = new SemanticExp(lLabels[0],SemanticExp.LABELCONV);
                    lOpExp.setSecondLabel(lLabels[1]);
                }
                else if(lOpName.matches("[a-z0-9]+")){
                    int i = findArrayIndex(lOpName, pVars);
                    if(i >= 0){
                        lOpExp = new SemanticExp(lOpName,SemanticExp.PARAM);
                        lOpExp.setParamIndex(i);
                        lOpExp.setParamGroup(pGroup[i]);
                    }
                    else{
                        String lError = "Invalid symbol " + lOpName + ". i=" + i + ", pVars = [";
                        for(i = 0; i < pVars.length - 1; i++){
                            lError += pVars[i];
                            lError += ", ";
                        }
                        lError += pVars[i];
                        lError += "]";
                        
                        throw new ParseException(lError);
                    }
                }
                else {
                    throw new ParseException("OpName " + lOpName + " is invalid.");
                }
    
                pOpExp.addOperation(lOpExp);
            }
            else if(pStr.charAt(lIndex) == '('){
                lOpExp = new SemanticExp(lOpName, SemanticExp.FUNC);
                int i = findArrayIndex(lOpName,pVars);
                if(i >= 0){
                    lOpExp.setParamIndex(i);
                }
                
                lIndex = parseOperation(pStr, lIndex+1, lOpExp, pVars, pGroup);
                pOpExp.addOperation(lOpExp);
            }
            
            lIndex = skip(pStr, lIndex, " \t\n");
            // Skip the comma or end paranthesis.
            if(lIndex >= pStr.length() || pStr.charAt(lIndex) == ')'){
                break;
            }
            lIndex++;
        }
        return lIndex+1;
    }
    
    private int findArrayIndex(String pStr, String pArray[] ){
        if (pArray == null){
            return -1;
        }
        for(int i = 0 ; i < pArray.length ; i++){
            if(pArray[i].equals(pStr)){
                return i;
            }
        }
        return -1;
    }
    
    private LabelExp parseLabelExp(String pLabel) throws ParseException {
        String lTemp = pLabel.toUpperCase();
        if (lTemp.compareTo(pLabel) != 0){
            throw new ParseException("parseLabelExp: Invalid label at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
        }
        return new LabelExp(pLabel);
    }
    
    private String checkString(String pString,String pPrefix,String pSuffix) throws ParseException{
        if (pString.startsWith(pPrefix) && pString.endsWith(pSuffix)){
            return pString.substring(1, pString.length()-1);
        }
        else {
            throw new ParseException("checkStringExp: Invalid string " + "\"" + pString+ "\"" + " at rule " + mCurrentExpNr + ":\n" + mCurrentExp);
        }
    }
    
    private int findFirst(String pString, int pStartIndex, String pChars){
        int lMinIndex = pString.length();
        for (int i = 0; i < pChars.length(); i++){
            int lIndex = pString.indexOf(pChars.charAt(i), pStartIndex);
            if (lIndex >= 0 && lIndex < lMinIndex){
                lMinIndex = lIndex;
            }
        }
        return lMinIndex;
    }
    private int skip(String pString, int pStartIndex, String pChars){
        //System.out.println("Skipping spaces in \"" + pString.substring(pStartIndex) + "\"");
        int i = pStartIndex;
        while(i < pString.length() && pChars.indexOf(pString.charAt(i)) >= 0){
            i++;
        }
        return i;
    }
    
    private int mCurrentExpNr;
    private String mCurrentExp;
    private LinkedList<MapExp> mMapExpList;
    private LinkedList<ImplyExp> mGramExpList;
}
